package com.lt.integration.jpa.component;

import com.lt.integration.jpa.component.common.Direction;
import com.lt.integration.jpa.component.hibernate.MyResultTransformer;
import com.lt.integration.jpa.component.hibernate.Updater;
import com.lt.integration.jpa.page.Pagination;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Transactional
public class BaseDaoImpl<T extends BaseModel, QO extends BaseQO> extends CommonDao<T, QO> implements BaseDao<T, QO> {


	public BaseDaoImpl(Class<T> domainClass, EntityManager entityManager) {
		super(domainClass, entityManager);
	}

    private static final int BATCH_SIZE = 500;

	/**
	 * 根据id获取对象 调用findById方法 不条用getOne 延迟加载
	 * 
	 * @param id
	 * @return T
	 */
	@Override
	@Transactional(readOnly = true)
	public T get(Long id) {
		return super.findById(id).orElseGet(() -> null);
	}

	/**
	 * 保存
	 * @param entity
	 *  @return T
	 */
	@Override
	public T persist(T entity) {
		super.persist(entity);
		return entity;
	}

	/**
	 * 更新
	 * 
	 * @param entity
	 * @return T
	 */
	@Override
	public T update(T entity) {
		return super.saveAndFlush(entity);
	}

	/**
	 * 查询唯一对象
	 * 
	 * @param qo
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public T queryUnique(QO qo) {
		List<T> list = queryList(qo, 1);
		if (list != null && list.size() > 0) {
			return list.get(0);
		}

		return null;
	}

	/**
	 * 查询唯一对象 可查询关联对象
	 * 
	 * @param qo
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public T fetchUnique(QO qo) {
		List<T> list = queryList(qo);
		if (list != null && list.size() > 0) {
			return list.get(0);
		}
		return null;
	}

	/**
	 * 查询对象列表
	 * 
	 * @param qo
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public List<T> queryList(QO qo) {
		return queryList(qo, 0, null);
	}

	/**
	 * 查询实体列表
	 * 
	 * @param qo
	 * @param maxSize 最大获取数量
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public List<T> queryList(QO qo, Integer maxSize) {
		return queryList(qo, 0, maxSize);
	}

	/**
	 * 查询实体列表
	 * 
	 * @param qo
	 * @param offset  偏移量
	 * @param maxSize 最大获取数量
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public List<T> queryList(QO qo, Integer offset, Integer maxSize) {
		return queryList(null,qo,offset,maxSize);
	}

	/**
	 * 查询实体列表
	 *
	 * @param attributes	查询字段
	 * @param qo
	 * @param offset  偏移量
	 * @param maxSize 最大获取数量
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public List<T> queryList(List<Attribute<?, ?>> attributes,QO qo, Integer offset, Integer maxSize) {
		if (null == attributes || attributes.size() < 1) {// 查询全部字段

			// id不为空时 查询全部字段 直接通过id 查询信息
			Long id = qo.getId();

			if (null != id) {// 如果id不为空 直接通过id查询
				T t = getEntityManager().find(getDomainClass(), id);
				List<T> list = new ArrayList<>();
				list.add(t);
				return list;
			}

			CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
			CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(getDomainClass());

			Root<T> root = criteriaQuery.from(getDomainClass());

			// build query
			buildQuery(criteriaBuilder, criteriaQuery, root, qo);

			TypedQuery<T> typedQuery = getEntityManager().createQuery(criteriaQuery);

			if (offset != null && offset >= 0) {
				typedQuery.setFirstResult(offset);
			}
			if (maxSize != null && maxSize >= 1) {
				typedQuery.setMaxResults(maxSize);
			}

			List<T> result = typedQuery.getResultList();

			return result;
		} else {// 查询部分字段

			DetachedCriteria detachedCriteria = DetachedCriteria.forClass(getDomainClass());

			Session session = getEntityManager().unwrap(Session.class);
			Criteria criteria = detachedCriteria.getExecutableCriteria(session);

			List<String> propertys = new ArrayList<>();

			for (Attribute<?, ?> attr : attributes) {
				propertys.add(attr.getName());
			}

			ProjectionList projectionList = Projections.projectionList();
			for (String property : propertys) {
				projectionList.add(Projections.property(property), property);
			}

			criteria = this.buildCriteria(criteria, qo);

			criteria.setProjection(projectionList);
			criteria.setResultTransformer(new MyResultTransformer(getDomainClass()));

			if (offset != null && offset >= 0) {
				criteria.setFirstResult(offset);
			}
			if (maxSize != null && maxSize >= 1) {
				criteria.setMaxResults(maxSize);
			}

			@SuppressWarnings("unchecked")
			List<T> result = criteria.list();

			return result;
		}
	}

	/**
	 * 查询数量
	 * 
	 * @param qo
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public Long count(QO qo) {
		CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
		CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
		Root<T> root = criteriaQuery.from(getDomainClass());
		criteriaQuery.select(criteriaBuilder.count(root));

		buildQueryWithoutOrderBy(criteriaBuilder, criteriaQuery, root, qo);

		return getEntityManager().createQuery(criteriaQuery).getSingleResult();

	}

	/**
	 * 分页查询对象
	 *
	 * @param pagination
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public Pagination<T> queryPagination(Pagination<T> pagination) {
		if (!(pagination.getCondition() instanceof BaseQO)) {
			pagination.setCondition(null);
		}

		@SuppressWarnings("unchecked")
		QO qo = (QO) pagination.getCondition();

		Long totalCount = this.count(qo);

		Pagination<T> p = new Pagination(pagination.getPageNo(), pagination.getPageSize(), totalCount);

		if (totalCount < 1) {
			return p;
		}

		// 检查页码是否超过最后一页
		if (pagination.isCheckPassLastPageNo() && pagination.getPageNo() > p.getPageNo()) {
			return p;
		}

		List<T> list = this.queryList(qo, p.getStartIndex(), p.getPageSize());
		p.setCondition(qo);
		p.setList(list);
		return p;
	}

	/**
	 * 分页查询对象
	 * 
	 * @param qo
	 * @return
	 */
	@Override
	@Transactional(readOnly = true)
	public Pagination<T> queryPagination(QO qo) {
		Long totalCount = this.count(qo);

		Pagination<T> p = new Pagination(qo.getPageNo(), qo.getPageSize(), totalCount);

		if (totalCount < 1) {
			return p;
		}

		// 检查页码是否超过最后一页
		if (qo.getPageNo() > p.getPageNo()) {
			return p;
		}

		List<T> list = this.queryList(qo, p.getStartIndex(), p.getPageSize());
//		p.setCondition(qo);
		p.setList(list);
		return p;
	}

	@Override
	@Transactional(readOnly = true)
	public Pagination<T> queryPagination(Long id, QO qo) {
		return this.queryPagination(id, qo, Direction.ASC);
	}

	@Override
	@Transactional(readOnly = true)
	public Pagination<T> queryPagination(Long id, QO qo, Direction direction) {
		Pagination<T> pagination = new Pagination();
		pagination.setPageSize(qo.getPageSize());
//		pagination.setCondition(qo);

		CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
		CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(getDomainClass());

		Root<T> root = criteriaQuery.from(getDomainClass());

		qo.setCursor(id, direction);

		// build query
		criteriaQuery = buildQuery(criteriaBuilder, criteriaQuery, root, qo);

		TypedQuery<T> typedQuery = getEntityManager().createQuery(criteriaQuery);
		typedQuery.setMaxResults(pagination.getPageSize());
		List<T> result = typedQuery.getResultList();

		pagination.setList(result);

		return pagination;
	}

	/**
	 * 批量删除
	 */
	@Override
	public void batchDelete(List<T> entities) {
		this.deleteInBatch(entities);
	}

    /**
     * 批量保存
     * @param entities
     * @return
     */
    @Override
    public Iterable<T> batchSave(Iterable<T> entities) {
        Iterator<T> iterator = entities.iterator();
        int index = 0;
        while (iterator.hasNext()){
            getEntityManager().persist(iterator.next());
            index++;
            if (index % BATCH_SIZE == 0){
                getEntityManager().flush();
                getEntityManager().clear();
            }
        }
        if (index % BATCH_SIZE != 0){
            getEntityManager().flush();
            getEntityManager().clear();
        }
        return entities;
    }

    /**
     * 批量更新
     * @param entities
     * @return
     */
    @Override
    public Iterable<T> batchUpdate(Iterable<T> entities) {
        Iterator<T> iterator = entities.iterator();
        int index = 0;
        while (iterator.hasNext()){
            getEntityManager().merge(iterator.next());
            index++;
            if (index % BATCH_SIZE == 0){
                getEntityManager().flush();
                getEntityManager().clear();
            }
        }
        if (index % BATCH_SIZE != 0){
            getEntityManager().flush();
            getEntityManager().clear();
        }
        return entities;
    }

    @Override
	public int update(Updater updater) {
		return updateByUpdater(updater);
	}

	@Override
	public int update(Long id, Attribute<?, ?> attribute, Object value) {
		Updater updater = Updater.instance().id(BaseModel_.id, id).include(attribute, value);
		return this.update(updater);
	}

	@Override
	public int update(Long id, Map<Attribute<?, ?>, Object> params) {
		Updater updater = Updater.instance().id(BaseModel_.id, id);

		for (Map.Entry<Attribute<?, ?>, Object> map : params.entrySet()) {
			updater.include(map.getKey(), map.getValue());
		}
		return this.update(updater);
	}

	@Override
	@Transactional(readOnly = true)
	public boolean exists(QO qo) {
		return this.count(qo) > 0;
	}
}
